package com.thd.springboot.project.camundamanager.service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

import com.thd.springboot.framework.utils.JacksonUtil;
import com.thd.springboot.framework.utils.MyListUtils;
import com.thd.springboot.framework.utils.MyStringUtils;
import com.thd.springboot.project.camundamanager.config.IdentityLinkTypeEnum;
import com.thd.springboot.project.camundamanager.constant.ProcConstant;
import com.thd.springboot.project.camundamanager.constant.ProcessInstanceState;
import com.thd.springboot.project.camundamanager.dto.CompleteTaskDto;
import com.thd.springboot.project.camundamanager.dto.StartProcessInstanceDto;
import com.thd.springboot.project.camundamanager.dto.UserOfTaskDto;
import com.thd.springboot.project.camundamanager.entity.*;
import com.thd.springboot.project.camundamanager.mapper.CamundaMapper;
import com.thd.springboot.project.camundamanager.utils.CamundaEntityTransferUtils;
import org.camunda.bpm.engine.*;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.history.HistoricTaskInstance;
import org.camunda.bpm.engine.history.HistoricVariableInstance;
import org.camunda.bpm.engine.repository.Deployment;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.runtime.ProcessInstantiationBuilder;
import org.camunda.bpm.engine.task.IdentityLink;
import org.camunda.bpm.engine.task.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * com.thd.processmanagementserver.service.impl.ProcessServiceImpl
 *
 * @author: wanglei62
 * @DATE: 2021/1/25 15:06
 **/
@Service("camundaService")
public class CamundaServiceImpl implements ProcessService {

    @Autowired
    private CamundaMapper camundaMapper;
    @Autowired
    private TaskService taskService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private HistoryService historyService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private IdentityService identityService;
    @Autowired
    private ProcessEngine processEngine;
    @Autowired
    private FormService formService;


    private Logger logger = LoggerFactory.getLogger(CamundaServiceImpl.class);

    @Override
    public ProcDeployEntity deployProcDef(InputStream is, String bpmnName) {

        logger.info("================ deployProcDef [" + bpmnName	+ "] Start ===================");

        Deployment deployment = repositoryService.createDeployment().addInputStream(bpmnName,is).deploy();
        ProcDeployEntity procDeployEntity = CamundaEntityTransferUtils.transfer(deployment);
        logger.info("---- Deploy ID：" + deployment.getId());
        logger.info("================ deployProcDef [" + bpmnName	+ "] END ===================");

        return procDeployEntity;
    }

    @Override
    public void deleteProcDefDeployById(String deploymentId) {
        logger.info("================ deleteProcDefDeployById deploymentId:[" + deploymentId	+ "] Start ===================");
        this.repositoryService.deleteDeployment(deploymentId,true);
        logger.info("================ deleteProcDefDeployById deploymentId:[" + deploymentId	+ "] END ===================");
    }

    @Override
    public ProcDefEntity queryProcDefById(String procDefId) {
        ProcessDefinition processDefinition = this.repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId).singleResult();
        ProcDefEntity procDefEntity = CamundaEntityTransferUtils.transfer(processDefinition);
        return procDefEntity;
    }

    @Override
    public PageInfo<ProcDefEntity> queryProcessDefPage(ProcDefEntity condition) {
        PageHelper.startPage(condition.getCurrent(), condition.getPageSize()).setOrderBy(condition.getOrderBy());
        PageInfo<ProcDefEntity> pageInfo = new PageInfo<ProcDefEntity>(this.camundaMapper.queryProcessDef(condition));
        return pageInfo;
    }

    @Override
    public List<ProcDefEntity> queryProcessDefList(ProcDefEntity condition) {
        return this.camundaMapper.queryProcessDef(condition);
    }

    @Override
    public void deleteProcessDefById(String processDefId){
        this.repositoryService.deleteProcessDefinition(processDefId,true);
    };

    @Override
    public ProcessInstanceEntity startProcess(StartProcessInstanceDto startProcessInstanceDto) {

        logger.info("================ startProcess Start ===================");
        logger.info("================ startProcess Start Information : {} ===================", JacksonUtil.objToJson(startProcessInstanceDto));

        ProcessInstantiationBuilder processInstantiationBuilder = null;
        if(MyStringUtils.isNotEmpty(startProcessInstanceDto.getProcDefKey())){
            processInstantiationBuilder = runtimeService.createProcessInstanceByKey(startProcessInstanceDto.getProcDefKey());
        } else if (MyStringUtils.isNotEmpty(startProcessInstanceDto.getProcDefId())) {
            processInstantiationBuilder = runtimeService.createProcessInstanceById(startProcessInstanceDto.getProcDefId());
        }else{
            throw new RuntimeException("one of procDefKey or procDefId must be initial");
        }

        //TODO 验证businessKey是否存在且不重复
        if(MyStringUtils.isEmpty(startProcessInstanceDto.getBusinessKey())){
            throw new RuntimeException("businessKey can not be empty");
        }

        processInstantiationBuilder.businessKey(startProcessInstanceDto.getBusinessKey());

        if(MyStringUtils.isNotEmpty(startProcessInstanceDto.getActivitiId())){
            //某节点之前开始
            processInstantiationBuilder.startBeforeActivity(startProcessInstanceDto.getActivitiId());
        }

        if(null != startProcessInstanceDto.getProcessVar()){
            processInstantiationBuilder.setVariables(startProcessInstanceDto.getProcessVar());
        }
        ProcessInstance processInstance = processInstantiationBuilder.execute();
        logger.info("================ startProcess End ===================");
        return CamundaEntityTransferUtils.transfer(processInstance);
    }

    @Override
    public ProcessInstanceEntity queryProcessInstanceByInstanceId(String processInstanceId) {
        ProcessInstance processInstance = this.runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();

        HistoricProcessInstance processInstanceHis = this.historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();

        ProcessInstanceEntity processInstanceEntity ;
        if(null != processInstance ){
            processInstanceEntity = CamundaEntityTransferUtils.transfer(processInstance);

            processInstanceEntity.setProcessInstanceStartTime(processInstanceHis.getStartTime());
            processInstanceEntity.setProcessInstanceEndTime(processInstanceHis.getEndTime());

            if(processInstance.isSuspended()){
                processInstanceEntity.setState(ProcessInstanceState.SUSPEND);
            }else{
                processInstanceEntity.setState(ProcessInstanceState.NORMAL);
            }
        }else{
            processInstanceEntity = CamundaEntityTransferUtils.transfer(processInstanceHis);
            processInstanceEntity.setState(ProcessInstanceState.FINISH);
        }

        return processInstanceEntity;
    }

    @Override
    public ProcessInstanceEntity queryProcessInstanceDetailByInstanceId(String processInstanceId){
        ProcessInstanceEntity processInstanceEntity = this.queryProcessInstanceByInstanceId(processInstanceId);
        List<ProcessInstanceTaskEntity> hisTaskList = this.queryTaskHisList(processInstanceId);
        List<ProcessInstanceTaskEntity> currentTaskList = this.queryTaskList(processInstanceId);
        processInstanceEntity.setHisTaskList(hisTaskList).setCurrentTaskList(currentTaskList);
        return processInstanceEntity;
    };


    @Override
    public void suspendsProcessInstance(String processInstanceId) {
        this.runtimeService.suspendProcessInstanceById(processInstanceId);
    }

    @Override
    public void activateProcessInstance(String processInstanceId) {
        this.runtimeService.activateProcessInstanceById(processInstanceId);
    }

    @Override
    public void cancelProcessInstance(String processInstanceId, String deleteReason) {
        this.runtimeService.deleteProcessInstance(processInstanceId,deleteReason);
    }

    @Override
    public List<ProcessInstanceExecutionEntity> queryCurrentExecutes(String processInstanceId) {
        return null;
    }

    @Override
    public PageInfo<ProcessInstanceTaskEntity> queryTaskPage(ProcessInstanceTaskEntity condition) {
        PageHelper.startPage(condition.getCurrent(), condition.getPageSize()).setOrderBy(condition.getOrderBy());
        PageInfo<ProcessInstanceTaskEntity> pageInfo = new PageInfo<ProcessInstanceTaskEntity>(this.camundaMapper.queryTask(condition));
        return pageInfo;
    }

    @Override
    public List<ProcessInstanceTaskEntity> queryTaskList(ProcessInstanceTaskEntity condition) {
        return this.camundaMapper.queryTask(condition);
    }

    @Override
    public List<ProcessInstanceTaskEntity> queryTaskList(String processInstanceId) {
        ProcessInstanceTaskEntity entity = ProcessInstanceTaskEntity.createInstance();
        entity.setProcessInstanceId(processInstanceId);
        return this.queryTaskList(entity);
    }

    @Override
    public ProcessInstanceTaskEntity queryTaskByTaskId(String taskId) {
        Task task = this.taskService.createTaskQuery().taskId(taskId).singleResult();

        ProcessInstanceTaskEntity entity;
        if(null != task){
            entity  = CamundaEntityTransferUtils.transfer(task);
            entity.setCandidates(this.queryCandidateForTask(taskId));
            Map variableForTask = this.queryVariableByTaskId(taskId);
            entity.setTaskVar(variableForTask);
            entity.setExecutionVar(this.runtimeService.getVariables(task.getExecutionId()));
            entity.setProcessVariable(this.runtimeService.getVariables(task.getProcessInstanceId()));
        }else{
            HistoricTaskInstance taskHis = this.historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult();
            entity = CamundaEntityTransferUtils.transfer(taskHis);
            entity.setExecutionId(taskHis.getExecutionId());
        }
        return entity;
    }

    @Override
    public ProcessInstanceTaskEntity queryTaskHisByTaskId(String taskId) {
        HistoricTaskInstance hisTask = this.historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult();
        ProcessInstanceTaskEntity hisTaskEntity = CamundaEntityTransferUtils.transfer(hisTask);
//        List<HistoricVariableInstance> varList = this.historyService.createHistoricVariableInstanceQuery().taskIdIn(taskId).list();
//        List<HistoricVariableInstance> varList2 = this.historyService.createNativeHistoricVariableInstanceQuery().list();

        return hisTaskEntity;
    }

    @Override
    public List<String> queryCandidateForTask(String taskId) {
        List<IdentityLink> l = this.taskService.getIdentityLinksForTask(taskId);
        List<String> r = l.stream().filter(item -> {
            return IdentityLinkTypeEnum.CANDIDATE.getAlias().equals(item.getType());
        }).map(item -> item.getUserId()).collect(Collectors.toList());
        return r;
    }

    @Override
    public List<ProcessInstanceTaskEntity> queryTaskForAssignee(String assignee) {
        List<Task> taskList = this.taskService.createTaskQuery().taskAssignee(assignee).list();
        List<ProcessInstanceTaskEntity> r = taskList.stream().map( CamundaEntityTransferUtils::transfer).collect(Collectors.toList());
        return r;
    }

    @Override
    public List<ProcessInstanceTaskEntity> queryTaskForCandidate(String candidate) {

        List<Task> taskList = taskService.createTaskQuery().taskCandidateUser(candidate).list();
        List<ProcessInstanceTaskEntity> r = taskList.stream().map( CamundaEntityTransferUtils::transfer).collect(Collectors.toList());
        return r;
    }

    @Override
    public List<ProcessInstanceTaskEntity> queryTaskForUser(String assigneeOrcandidate) {

        List<ProcessInstanceTaskEntity> taskList = new ArrayList<ProcessInstanceTaskEntity>();
        taskList.addAll(queryTaskForAssignee(assigneeOrcandidate));
        taskList.addAll(queryTaskForCandidate(assigneeOrcandidate));
        return taskList;
    }

    @Override
    public List<ProcessInstanceTaskEntity> queryCurrentTaskByProcessInstanceId(String processInstanceId) {
//        List<Task> taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
//        List<ProcessInstanceTaskEntity> r = taskList.stream().map( CamundaEntityTransferUtils::transfer).collect(Collectors.toList());
//        return r;

        ProcessInstanceTaskEntity condition = ProcessInstanceTaskEntity.createInstance();
        condition.setProcessInstanceId(processInstanceId);
        return this.queryTaskList(condition);
    }

    @Override
    public List<ProcessInstanceTaskEntity> queryCurrentTaskByBusinessKey(String businessKey) {
//        List<Task> taskList = taskService.createTaskQuery().processInstanceBusinessKey(businessKey).list();
//        List<ProcessInstanceTaskEntity> r = taskList.stream().map( CamundaEntityTransferUtils::transfer).collect(Collectors.toList());
//        return r;
        ProcessInstanceTaskEntity condition = ProcessInstanceTaskEntity.createInstance();
        condition.setBusinessKey(businessKey);
        return this.queryTaskList(condition);
    }

    @Override
    public void completeTask(CompleteTaskDto completeTaskDto) {
        if(MyStringUtils.isEmpty(completeTaskDto.getTaskId())){
            throw new RuntimeException("Task Id can not be empty");
        }
        if(MyStringUtils.isNotEmpty(completeTaskDto.getOperator())){
            this.taskService.setAssignee(completeTaskDto.getTaskId(),completeTaskDto.getOperator());
        }
        this.taskService.complete(completeTaskDto.getTaskId(),completeTaskDto.getProcessVar());
    }



    @Override
    public void claimTask(String taskId, String userId) {
        this.taskService.claim(taskId,userId);
    }

    @Override
    public void claimAndComplateTask(String taskId, String userId, Map<String, Object> variableMap) {
        this.claimTask(taskId,userId);
        this.taskService.complete(taskId,variableMap);
    }

    @Override
    public void addVariableToProcessInstance(String executionId,Map map) {
        this.runtimeService.setVariables(executionId,map);
    }
    @Override
    public void addVariableToTask(String taskId,Map map){
        this.taskService.setVariables(taskId,map);
    };

    /**
     * 根据任务ID查询流程变量
     * @param taskId 任务ID
     * @return
     */
    public Map<String,Object> queryVariableByTaskId(String taskId){
        return this.taskService.getVariables(taskId);
    };
    /**
     * 根据执行ID查询流程变量
     * @param executionId 执行ID
     * @return
     */
    public Map<String,Object> queryVariableByExecutionId(String executionId){
        return this.runtimeService.getVariables(executionId);
    };
    /**
     * 根据流程实例ID查询流程变量
     * @param processInstanceId 流程实例ID
     * @return
     */
    public Map<String,Object> queryVariableByProcessInstanceId(String processInstanceId){
        return this.runtimeService.getVariables(processInstanceId);
    };

    @Override
    public void addCandidateUserToTask(String taskId, String userId) {
        this.taskService.addCandidateUser(taskId,userId);

    }

    @Override
    public void deleteCandidateUserFromTask(String taskId, String userId) {
        this.taskService.deleteCandidateUser(taskId,userId);
    }



    /**
     * 为某个Task添加候选人/待办人
     * @param userOfTaskDto
     */
    public void addUserToTask(UserOfTaskDto userOfTaskDto){


        if(MyListUtils.isNotEmpty(userOfTaskDto.getCandidates())){
            // 已经存在的候选人
            List<String> existedCandidates = this.queryCandidateForTask(userOfTaskDto.getTaskId());

            userOfTaskDto.getCandidates().forEach(item -> {
                if(!existedCandidates.contains(item)) { // 如果当前没有该候选选人才添加
                    this.addCandidateUserToTask(userOfTaskDto.getTaskId(), item);
                }
            });
        }

        if(MyStringUtils.isNotEmpty(userOfTaskDto.getAssignee())){
            this.taskService.setAssignee(userOfTaskDto.getTaskId(), userOfTaskDto.getAssignee());
        }
    };

    @Override
    public void removeUserToTask(UserOfTaskDto userOfTaskDto){
        if(MyListUtils.isNotEmpty(userOfTaskDto.getCandidates())){
            userOfTaskDto.getCandidates().forEach(item -> {
                this.deleteCandidateUserFromTask(userOfTaskDto.getTaskId(),item);
            });
        }
    };

    @Override
    public void delegateTask(String taskId,String delegateUserId){
        this.taskService.delegateTask(taskId,delegateUserId);
    };

    @Override
    public void resolveTask(String taskId){
        this.taskService.resolveTask(taskId);
    };



    @Override
    public void replaceCandidateUserFromTask(String taskId, String oldUserId, String newUserId) {
        this.deleteCandidateUserFromTask(taskId,oldUserId);
        this.addCandidateUserToTask(taskId,newUserId);
    }

    @Override
    public PageInfo<ProcessInstanceTaskHisEntity> queryTaskHisPage(ProcessInstanceTaskHisEntity condition) {
        return null;
    }

    @Override
    public List<ProcessInstanceTaskEntity> queryTaskHisList(ProcessInstanceTaskEntity condition) {
        List<ProcessInstanceTaskEntity> l = this.camundaMapper.queryTaskHis(condition);
        return l;
    }

    @Override
    public List<ProcessInstanceTaskEntity> queryTaskHisList(String processInstanceId){
        ProcessInstanceTaskEntity taskEntity = ProcessInstanceTaskEntity.createInstance();
        taskEntity.setProcessInstanceId(processInstanceId);
        return this.queryTaskHisList(taskEntity);
    };

    @Override
    public PageInfo<ProcessInstanceEntity> queryProcessInstancePage(ProcessInstanceEntity condition) {
        PageHelper.startPage(condition.getCurrent(), condition.getPageSize()).setOrderBy(condition.getOrderBy());
        PageInfo<ProcessInstanceEntity> pageInfo = new PageInfo<ProcessInstanceEntity>(this.camundaMapper.queryProcessInstance(condition));
        return pageInfo;
    }

    @Override
    public void freedomJump(String processInstanceId, String activitiId, Map<String, Object> variable) {

    }

    @Override
    public ProcDefEntity queryProcDefByDefId(String procDefId) {
        return null;
    }

    @Override
    public ProcDeployEntity queryProcDeployByDeployId(String procDeployId) {
        return null;
    }

    @Override
    public ProcInfo queryProcInfo(ProcConstant type, String id) {
        return null;
    }

    @Override
    public List<ProcDefEntity> queryAllProcDefKeyList() {
        return null;
    }
}
